﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Tcp;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Ftp
{
    internal partial class FtpSession : UnpackerAbs, IDisposable
    {
        private readonly FtpServer _ftpServer;
        public FtpServer FtpServer
        {
            get { return _ftpServer; }
        }

        private readonly NetTcpClient _client;
        private readonly Action<FtpSession> _sessionCloseCallback;
        private readonly NetLinkInfo _linkInfo;
        public NetLinkInfo LinkInfo
        {
            get { return _linkInfo; }
        }


        private FtpSessionStatus _sessionStatus = FtpSessionStatus.NoAuth;
        public FtpSessionStatus SessionStatus
        {
            get { return _sessionStatus; }
            set { _sessionStatus = value; }
        }

        private FtpUserInfo _ftpUser = null;
        public FtpUserInfo FtpUser
        {
            get { return _ftpUser; }
            set { _ftpUser = value; }
        }


        /// <summary>
        /// 重命名旧名称
        /// </summary>
        public string RenameOldName { get; set; }


        private FtpDataTransferFormats _dataTransferFormat = FtpDataTransferFormats.ASCII;
        public FtpDataTransferFormats DataTransferFormat
        {
            get { return _dataTransferFormat; }
            set
            {
                _dataTransferFormat = value;
            }
        }

        private FtpDataTranferAbs _tempDataTranferObject = null;


        public void SetDataTranferObject(FtpDataTranferAbs tempDataTranfer)
        {
            lock (this)
            {
                if (this._tempDataTranferObject != null)
                {
                    this._tempDataTranferObject.Dispose();
                }
                this._tempDataTranferObject = tempDataTranfer;
            }
        }

        public FtpDataTranferAbs GetDataTranferObject()
        {
            lock (this)
            {
                var temp = this._tempDataTranferObject;
                this._tempDataTranferObject = null;
                return temp;
            }
        }



        private readonly List<FtpDataTranferAbs> _dataTranferList = new List<FtpDataTranferAbs>();
        private readonly object _dataTranferListLock = new object();
        public void AddDataTranfer(FtpDataTranferAbs tempDataTranfer)
        {
            lock (this._dataTranferListLock)
            {
                this._dataTranferList.Add(tempDataTranfer);
            }
        }

        public void RemoveDataTranfer(FtpDataTranferAbs tempDataTranfer)
        {
            lock (this._dataTranferListLock)
            {
                this._dataTranferList.Remove(tempDataTranfer);
            }
        }

        public void AbortDataTranfer()
        {
            lock (this._dataTranferListLock)
            {
                foreach (FtpDataTranferAbs dataTranfer in this._dataTranferList)
                {
                    dataTranfer.Dispose();
                }
                this._dataTranferList.Clear();
            }
        }


        /// <summary>
        /// 断点续传文件位置
        /// </summary>
        public long RestPosition { get; set; } = 0;

        private Encoding _encoding = null;
        /// <summary>
        /// 获取编码
        /// </summary>
        public Encoding Encoding
        {
            get { return this._encoding; }
            set
            {
                if (value == null)
                {
                    this._encoding = Encoding.UTF8;
                }
                else
                {
                    this._encoding = value;
                }
            }
        }



        private static readonly byte[] _newLineBuffer;
        private static readonly char[] _newLineChars;

        static FtpSession()
        {
            _newLineBuffer = Encoding.ASCII.GetBytes(Environment.NewLine);
            _newLineChars = new char[] { '\r', '\n' };
        }




        public FtpSession(FtpServer ftpServer, Socket client, Action<FtpSession> sessionCloseCallback)
        {
            var initPara = new NetTcpClientInitPara(this, client)
            {
                ReceiveThreadName = $"ftp控制命令接收线程{NetHelper.GetIPPortString(client.RemoteEndPoint)}",
                Certificate = ftpServer.Config.Certificate
            };
            this._client = new NetTcpClient(initPara);
            this._client.ConnectChanged += _client_ConnectChanged;

            this._ftpServer = ftpServer;
            this._sessionCloseCallback = sessionCloseCallback;
            this._linkInfo = this._client.LinkInfo;
            this.Encoding = ftpServer.Config.Encoding;
        }

        private void _client_ConnectChanged(object sender, NetConnectedChangedArgs e)
        {
            if (e.Status == ConnectionStatus.Closed)
            {
                this.OnRaiseSessionCloseCallback();
            }
        }

        private byte[] _buffer = new byte[200];
        private int _offset = 0;
        private int _dateLen = 0;

        /// <summary>
        /// 对数据进行拆包
        /// </summary>
        /// <param name="unpackerPara">分包参数</param>
        /// <returns>拆包成功的数据长度</returns>
        protected override int PrimitiveUnPackge(IUnpackerPara unpackerPara)
        {
            var np = (NetTcpUnpackerPara)unpackerPara;
            if (np.DataLength == 0)
            {
                return 0;
            }

            try
            {
                //注:此处没有直接使用np.Buffer,是因为收到的数据可能是加密的.通过np.Reader.Read得到的数据是解密后的

                if (this._dateLen + np.DataLength > this._buffer.Length)
                {
                    //每次扩展的时候,比需要的长度再多100字节
                    var tmpBuffer = new byte[this._dateLen + np.DataLength + 100];
                    Array.Copy(this._buffer, this._offset, tmpBuffer, 0, this._dateLen);
                    this._buffer = tmpBuffer;
                    this._offset = 0;
                }
                else if (this._offset + this._dateLen + np.DataLength > this._buffer.Length)
                {
                    //将处于数组中间某个位置的数据移动到数组起始位置
                    Array.Copy(this._buffer, this._offset, this._buffer, 0, this._dateLen);
                    this._offset = 0;
                }

                int len = 0;
                len = np.Reader.Read(this._buffer, this._offset + this._dateLen, np.DataLength);
                this._dateLen += len;

                int index = EnumerableEx.FindCollection<byte>(this._buffer, this._offset, this._dateLen, _newLineBuffer);
                if (index >= 0)
                {
                    int count = index + _newLineBuffer.Length - this._offset;
                    string commandLine = this._encoding.GetString(this._buffer, this._offset, count);
                    this._dateLen -= count;
                    this._offset += count;

                    //ZLoger.Debug(string.Format("ftp收到来自{0}：[{1}]", NetHelper.GetIPPortString(this._linkInfo.RemoteEndPoint), commandLine));

                    //处理收到的控制命令行
                    commandLine = commandLine.Trim(_newLineChars).Trim(_newLineChars);
                    this.ProcessControlCommand(commandLine);
                    return len;
                }

                return len;
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
                return 0;
            }
        }


        internal void OnRaiseSessionCloseCallback()
        {
            this._sessionCloseCallback(this);
        }


        private string _lastCommand = null;
        /// <summary>
        /// 前一个执行的命令
        /// </summary>
        public string LastCommand
        {
            get { return _lastCommand; }
        }

        public bool LastCommandIsTranferCommand()
        {
            //注:各个版本的客户端顺序蛋疼,不再作验证了.很无奈啊!...

            //string lastCommand = this._lastCommand;
            //if (string.Equals(lastCommand, FtpCommands.PASV) ||
            //    string.Equals(lastCommand, FtpCommands.PORT)||
            //    string.Equals(lastCommand, FtpCommands.CWD))
            //{
            return true;
            //}

            //return false;
        }


        private void ProcessControlCommand(string commandLine)
        {
            try
            {
                // 分解客户端发来的控制信息中的命令和参数
                string commandName = commandLine;
                string commandParam;
                ZLoger.Debug("----------------------------");
                ZLoger.Debug($"收到命令:{commandLine}");
                int index = commandLine.IndexOf(' ');
                if (index != -1)
                {
                    commandName = commandLine.Substring(0, index).ToUpper();
                    commandParam = commandLine.Substring(index).Trim();
                }
                else
                {
                    commandParam = null;
                }

                commandName = commandName.ToUpper();
                IFtpCommand command;
                if (FtpCommandManager.TryGetFtpCommand(commandName, out command))
                {
                    command.Excute(this, commandName, commandParam);
                    this._lastCommand = commandName;
                }
                else
                {
                    var cmds = FtpCommandManager.GetCommands();
                    string allCmd = string.Join("\r\n", cmds);
                    ZLoger.Warn($"未实现的ftp命令\"{commandName}\",本站点支持以下命令:\r\n{allCmd}");
                    this.Send(new FtpCodeResponse(FtpStatusCodes.Code202));
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);

                try
                {
                    this.Send(new FtpCodeResponse(FtpStatusCodes.Code451));
                }
                catch (IOException ioex)
                {
                    ZLoger.Warn(ioex);
                    this.OnRaiseSessionCloseCallback();
                }
                catch (Exception ex2)
                {
                    ZLoger.Error(ex2);
                    this.OnRaiseSessionCloseCallback();
                }
            }
        }


        internal void Send(IFtpResponse res)
        {
            if (res == null)
            {
                return;
            }

            string message = res.Message;
            if (message != null)
            {
                ZLoger.Debug($"响应命令:{message}");
                byte[] buffer = this._encoding.GetBytes(message);
                this._client.Send(buffer);
                this._client.Send(_newLineBuffer);
                //ZLoger.Debug(string.Format("响应ftp{0},内容：[{1}]", NetHelper.GetIPPortString(this._linkInfo.RemoteEndPoint), message));
            }
        }


        public void StartReceiveCommand()
        {
            //建立连接后先响应下面这条信息
            //Response: 220 Microsoft FTP Service
            this.Send(new FtpCodeResponse(FtpStatusCodes.Code220, "UtilZ FTP Server v1.0"));
        }


        public bool AllowRead()
        {
            var ftpUser = this._ftpUser;
            if (ftpUser == null)
            {
                return false;
            }

            switch (ftpUser.AccessPolicy)
            {
                case FtpAccessPolicy.Read:
                case FtpAccessPolicy.ReadAndWrite:
                    return true;
                default:
                    return false;
            }
        }

        public bool AllowWrite()
        {
            var ftpUser = this._ftpUser;
            if (ftpUser == null)
            {
                return false;
            }

            switch (ftpUser.AccessPolicy)
            {
                case FtpAccessPolicy.Write:
                case FtpAccessPolicy.ReadAndWrite:
                    return true;
                default:
                    return false;
            }
        }



        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="isDisposing"></param>
        protected override void Dispose(bool isDisposing)
        {
            try
            {
                this._client.Dispose();

                if (this._tempDataTranferObject != null)
                {
                    this._tempDataTranferObject.Dispose();
                    this._tempDataTranferObject = null;
                }

                this.AbortDataTranfer();

                base.Dispose(isDisposing);
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }
    }
}
